﻿using System;
using System.Collections.Generic;

namespace LoRel
{
    public class Table {

        private const string PRIMARY_INDEX_NAME = "PRIMARY KEY";

        private readonly TableReference m_Table;
        private readonly Index m_PrimaryIndex;
        private readonly Type m_ExternalType;
        private readonly Proxy m_PrimaryProxy;

        //private readonly Dictionary< Key, TableRow > m_UniqueLookup;
        private readonly Dictionary<String, Index> m_Indices;

        public Table( Type external_type, TableReference schema ) {
            m_Table = schema;
            m_Indices = new Dictionary< string, Index >();
            m_ExternalType = external_type;

            m_PrimaryIndex = CreateIndex( PRIMARY_INDEX_NAME, new ColumnSet( m_Table.Keys.Columns ) );
        }

        private Index CreateIndex(String index_name, ColumnSet columns) {
            // TODO: use Core.GetProxy or whatever. We don't want too many of these littering everything.
            // also, I think I can just take a subset of proxy...
            Proxy proxy = Proxy.CreateProxy(m_ExternalType, columns);
            Index index;
            if (columns == m_Table.Keys.Columns)
            {
                index = new Index(proxy, proxy);
            }
            else
            {
                index = new Index(m_PrimaryProxy, proxy);

            }
            m_Indices.Add(index_name, index);
            return index;
        }
        public Index DeclareIndex(String index_name, ColumnSet columns) {
            Index index = CreateIndex( index_name, columns );
            m_PrimaryIndex.IndexChangePending += index.EnqueueUpdate;
            return index;
        }

        public event IndexChangeDelegate TableChangePending {
            add { m_PrimaryIndex.IndexChangePending += value; }
            remove { m_PrimaryIndex.IndexChangePending -= value; }
        }

        public void RegisterListener(String index_name, IndexChangeDelegate handler) {
            Index index;
            if( TryGet( index_name, out index ) == false ){ throw new ArgumentException("No registered index.");}
            index.IndexChangePending += handler;
        }
        public void RegisterListener( Key specific_value, String index_name, IndexChangeDelegate handler) {
            Index index;
            if (TryGet(index_name, out index) == false) { throw new ArgumentException("No registered index."); }
            
            index.RegisterListener( specific_value, handler );
        }

        public NullReport GetKey( Object external_instance, out Key key ) {
            return m_PrimaryIndex.GetKey(external_instance, out key);
        }


        public bool TryGet( String name, out Index index ) {
            return m_Indices.TryGetValue( name, out index );
        }

        public bool TryGet(Key key, out TableRow row) {
            IndexBucket bucket;
            if (m_PrimaryIndex.TryGetBucket(key, out bucket) == false) {
                row = null;
                return false;
            }
            
            return bucket.TryGet( key, out row );
        }


        public void EnqueueUpdate( ChangeNoticeArgs args )
        {
            // so this is external data, we want to convert this to internal representation
            TableChangeArgs table_args = ConvertUpdates(args);
            //foreach( var pair in m_Indices ) {
            //    pair.Value.EnqueueUpdate( table_args );
            //}

            m_PrimaryIndex.EnqueueUpdate( table_args );
        }

        private TableChangeArgs ConvertUpdates( ChangeNoticeArgs args ) {
            List<TableChangeItem> change_items = new List< TableChangeItem >();

            foreach (ChangeNotifyItem item in args.ChangedItems ) {
                Key new_key = null, old_key = null;
                TableRow new_value = null, old_value = null;

                if( item.NewValue != null ) {
                    switch( GetKey( item.NewValue, out new_key ) ) {
                        case NullReport.AllPopulated:
                            break;
                        case NullReport.SomeNull:
                        case NullReport.AllNull:
                            break;
                        default:
                            throw new ArgumentOutOfRangeException();
                    }
                    new_value = new TableRow( new_key, item.NewValue );
                }
                if( item.OldValue != null ) {
                    switch( GetKey( item.OldValue, out old_key )) {
                        case NullReport.AllPopulated:
                            break;
                        case NullReport.SomeNull:
                            break;
                        case NullReport.AllNull:
                            break;
                        default:
                            throw new ArgumentOutOfRangeException();
                    }
                    TryGet(old_key, out old_value);
                }
                
                if( new_key != null && old_key != null ) {
                    if( new_key != old_key ) {
                        // still unsure if this should be supported or not. I don't think it should.
                        // Possibly supported with a "warning" message or something?
                        throw new NotImplementedException();
                    }
                }
                change_items.Add( new TableChangeItem( new_value, old_value ) );
            }

            return new TableChangeArgs( args.ChangeType, change_items.Count, change_items );
        }
    }
}
